/*
 * Copyright 2008 Mark Davidson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.doofer

import com.documentum.fc.client.*
import com.documentum.fc.common.*
import com.documentum.operations.*
import com.documentum.com.*

class ObjectUtil {

    def cu = new ContentUtil()
    def clientX

    /**
     * Constructor
     */
    ObjectUtil() {
        clientX = new DfClientX()
    }

    /**
     * Gets an IDfPersistentObject using various combinations of parameters
     * - e.g. IDfSession instance + object id/path/qualification. The named
     * parameters are:
     *
     * <ul>
     * <li>objectId - the object id (optional)</li>
     * <li>path - the object path (optional)</li>
     * <li>qualification - the DQL qualification (optional)</li>
     * <li>session - the repository session</li>
     * </ul>
     *
     * @param params The named parameters. See description for more info.
     * @throws DfException
     */
    def getObject(Map params) {
        def object = null
        def objectId = params.objectId
        def path = params.path
        def qual = params.qualification
        def session = params.session

        if(session) {
            if(objectId) {
                object = session.getObject(clientX.getId(objectId))
            } else if(path) {
                object = session.getObjectByPath(path)
            } else if(qual) {
                object = session.getObjectByQualification(qual)
            }
        }

        object
    }

    /**
     * Creates a persistent object and optionally links
     * and to a folder id or path. If a path is specified
     * the method will create the path if it does not exist.
     *
     * <ul>
     * <li>session - the session</li>
     * <li>objectType - the object type</li>
     * <li>objectName - the object name (optional)</li>
     * <li>link - folder id or path to link to (optional)</li>
     * <li>content - the content as a ByteArrayOutputStream (optional)</li>
     * <li>file - the content as a File (optional)</li>
     * <li>format - the format/content type (optional)</li>
     * <li>pageNum - the pageNum (optional)</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def createObject(Map params, Closure closure = null) {
        def session = params.session
        def objectType = params.objectType
        def objectName = params.objectName
        def link = params.link
        def content = params.content
        def file = params.file
        def format = params.format
        def pageNum = params.pageNum

        // create persistent object
        def object = session.newObject(objectType)

        // set attributes
        if(objectName) object.objectName = objectName
        if(link) {
            if(link.startsWith('/')) {
                createFolder(session: session, path: link)
            } // else assume it's an object id
            object.link(link)
        }
        if(format) {
            object.setContentType(format)
        }

        // set content
        if(content || file)
                cu.setContent(object: object, content: content, file: file, format: format, pageNum: pageNum)

        if(closure) closure(object)
        object.save()

        object
    }

    /**
     * Create folder for the specified path (or sub-type of). All elements
     * of the folder path will be created if they do not already exist.
     *
     * <ul>
     * <li>session - the session</li>
     * <li>path - the folder path</li>
     * <li>objectType - the object type</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def createFolder(Map params) {
        def session = params.session
        def path = params.path
        def objectType = params.objectType

        // check if folder already exists
        def folder = session.getFolderByPath(path)
        if(folder) return folder

        def pos = 0
        def object = null
        def currentPath = ''

        splitPath(path: path).each {
            currentPath = "$currentPath/$it"
            folder = session.getFolderByPath(currentPath)

            if(!folder) {
                if(pos == 0) {
                    object = createObject(session: session,
                                          objectType: 'dm_cabinet',
                                          objectName: it) as IDfFolder
                } else {
                    object = createObject(session: session,
                                          objectType: objectType ? objectType : 'dm_folder',
                                          objectName: it,
                                          link: object.objectId.id) as IDfFolder
                }
            } else {
                // folder already exists but record this
                // in case we need to link a sub folder to it
                object = folder
            }
            
            pos++
        }

        object
    }

    /**
     * Splits a path into an array of optionally fully pathed elements. The
     * named parameters are:
     *
     * <ul>
     * <li>path - the path to be split</li>
     * <li>absolute - flag indicating whether the absolutely path elements should be created</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def splitPath(Map params) {
        def path = params.path
        def absolute = params.absolute

        def pathArray = []
        def pos = 0
        def currentPath = ''
        def sep = '/'

        path.split(sep).each() {
            if(it) {
                if(absolute) {
                    currentPath = "$currentPath/$it"
                } else {
                    currentPath = it
                }
                pathArray[pos++] = currentPath
            }
        }

        pathArray
    }

    /**
     * Deletes a folder structure.
     *
     * <ul>
     * <li>session - the session</li>
     * <li>path - the path to walk from (optional)/li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def deltree(Map params) {
        def path = params.path
        def args = [session: params.session,
                    path: path,
                    recursive: true,
                    walkByWidth: false,
                    queryType: IDfQuery.DF_EXEC_QUERY]

        def invalidPaths = [null, '', '/', '/Resources', '/System', '/Temp', '/Templates']

        if(!invalidPaths.contains(path)) {
            walkObjects(args) { object ->
                object.destroyAllVersions()
            }
        }
    }

    /**
     * Walks through a folder structure, firing a closure on each
     * persistent object it encounters. The object will be cast to
     * either an IDfFolder, IDfDocument or IDfSysObject.
     *
     * <ul>
     * <li>session - the session</li>
     * <li>path - the path to walk from (optional)/li>
     * <li>recursive - whether to drilldown through the folders (optional)</li>
     * <li>walkByWidth - whether to walk by width (optional - defaults to walk by depth)/li>
     * <li>queryType - the query type (optional)</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def walkObjects(Map params, Closure closure) {
        def session = params.session
        def path = params.path ?: '/'
        def recursive = params.recursive
        def walkByWidth = params.walkByWidth
        def queryType = params.queryType
        def dql = null
        
            // following fix addresses apostrophes in folder names - thanks to paddy.tanguybrow 04 Nov 2009
            path = path.replaceAll("'", "''")

        if(path == '/') {
            dql = 'select object_name, r_object_id from dm_cabinet order by object_name, r_object_id'
        } else {
            if(path.endsWith('/')) path = path[0..-2]    // chop trailing slash
            dql = "select object_name, r_object_id from dm_sysobject where folder('$path') order by object_name, r_object_id"
        }

        def results = new QueryUtil().toList(session: session, dql: dql, queryType: queryType) { row ->
            row.folderPath = path
            row.objectPath = "$path/$row.object_name"
        }

        if(results) {
            for(attribs in results) {
                def object = session.getObject(clientX.getId(attribs.r_object_id))

                // cast to tbo
                if(object instanceof IDfFolder) {
                    object = object as IDfFolder
                } else if(object instanceof IDfDocument) {
                    object = object as IDfDocument
                } else {
                    object = object as IDfSysObject
                }

                if(object instanceof IDfFolder && !walkByWidth && recursive) {
                    walkObjects([session: session, path: attribs.objectPath, queryType: queryType, walkByWidth: walkByWidth, recursive: recursive], closure)
                }

                // handle callback
                if(closure) closure(object)

                if(object instanceof IDfFolder && walkByWidth && recursive) {
                    walkObjects([session: session, path: attribs.objectPath, queryType: queryType, walkByWidth: walkByWidth, recursive: recursive], closure)
                }
            }
        }
    }
    
    /**
     * Create folder for the specified path (or sub-type of). All elements
     * of the folder path will be created if they do not already exist.
     *
     * <ul>
     * <li>session - the session</li>
     * <li>sourcePath - the source file path</li>
     * <li>targetPath - the target file path</li>
     * <li>objectType - the object type</li>
     * <li>format - the format/content type</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def importDocument(Map params, Closure closure = null) {
        def session = params.session
        def objectName = params.objectName
        def sourcePath = params.sourcePath
        def targetPath = params.targetPath
        def objectType = params.objectType
        def format = params.format
        def document = null

        def operation = clientX.importOperation
        operation.session = session

        // set folder to link to
        def folder = createFolder(session: session, path: targetPath)
        operation.destinationFolderId = folder.objectId

        // set import settings
        def node = operation.add(sourcePath) as IDfImportNode

        node.filePath = sourcePath
        if(format) node.format = format
        node.docbaseObjectType = objectType
        if(objectName) node.newObjectName = objectName

        // import away..
        if(operation.execute()) {

            document  = node.getNewObject() as IDfDocument

            // process callback
            if(closure) closure(document)

            // commit any unsaved changes
            if(document.dirty) document.save()
        }
        
        // return document
        document
    }
}